namespace UserVoiceApi
{
    using System;
    using System.IO;
    using System.Security.Cryptography;
    using System.Text;
    using System.Web;

    public static class SSO
    {
        public static string CreateToken(string accountKey, string apiKey, string userDetails)
        {
            ValidateSSOSettings(accountKey, apiKey, userDetails);
            
            byte[] initVectorBytes = Encoding.UTF8.GetBytes("OpenSSL for Ruby"); //// DO NOT CHANGE
            byte[] keyBytesLong;
            using (var sha = new SHA1CryptoServiceProvider())
            {
                keyBytesLong = sha.ComputeHash(Encoding.UTF8.GetBytes(apiKey + accountKey));
            }

            var keyBytes = new byte[16];
            Array.Copy(keyBytesLong, keyBytes, 16);

            var textBytes = Encoding.UTF8.GetBytes(userDetails);
            for (var i = 0; i < 16; i++)
            {
                textBytes[i] ^= initVectorBytes[i];
            }

            var encrypted = AESEncryptStringToBytes(textBytes, keyBytes, initVectorBytes);
            var encoded = Convert.ToBase64String(encrypted);
            return HttpUtility.UrlEncode(encoded);
        }

        public static string CreateUserJson(UserDetails userSettings)
        {
            var userDetails = new StringBuilder();
            userDetails.AppendFormat(@"{{""display_name"":""{0}"",", userSettings.DisplayName);
            userDetails.AppendFormat(@"""avatar_url"":""{0}"",", userSettings.AvatarUrl);
            userDetails.AppendFormat(@"""admin"":""{0}"",", userSettings.Admin);
            userDetails.AppendFormat(@"""expires"":""{0}"",", userSettings.Expires);
            userDetails.AppendFormat(@"""url"":""{0}"",", userSettings.Url);
            userDetails.AppendFormat(@"""email"":""{0}"",", userSettings.Email);
            userDetails.AppendFormat(@"""allow_forums"":""{0}"",", userSettings.AllowForums);
            userDetails.AppendFormat(@"""deny_forums"":""{0}"",", userSettings.DenyForums);
            userDetails.AppendFormat(@"""updates"":""{0}"",", userSettings.Updates);
            userDetails.AppendFormat(@"""comment_updates"":""{0}"",", userSettings.CommentUpdates);
            userDetails.AppendFormat(@"""owner"":""{0}"",", userSettings.Owner);
            userDetails.AppendFormat(@"""locale"":""{0}"",", userSettings.Locale);
            userDetails.AppendFormat(@"""guid"":""{0}""}}", userSettings.Guid);

            return userDetails.ToString();
        }

        private static void ValidateSSOSettings(string accountKey, string apiKey, string userDetails)
        {
            if (string.IsNullOrEmpty(accountKey) ||
                string.IsNullOrEmpty(apiKey) ||
                string.IsNullOrEmpty(userDetails))
            {
                throw new ApplicationException("AccountKey, ApiKey and UserDetails properties are all required for SSO token creation");
            }
        }

        private static byte[] AESEncryptStringToBytes(byte[] textBytes, byte[] key, byte[] iV)
        {
            // Declare the stream used to encrypt to an in memory
            // array of bytes and the RijndaelManaged object
            // used to encrypt the data.
            using (var memoryStreamEncrypt = new MemoryStream())
            using (var aesAlg = new RijndaelManaged())
            {
                // Provide the RijndaelManaged object with the specified key and IV.
                aesAlg.Mode = CipherMode.CBC;
                aesAlg.Padding = PaddingMode.PKCS7;
                aesAlg.KeySize = 128;
                aesAlg.BlockSize = 128;
                aesAlg.Key = key;
                aesAlg.IV = iV;

                // Create an encrytor to perform the stream transform.
                ICryptoTransform encryptor = aesAlg.CreateEncryptor();

                // Create the streams used for encryption.
                using (var criptoEncrypt = new CryptoStream(memoryStreamEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    criptoEncrypt.Write(textBytes, 0, textBytes.Length);
                    criptoEncrypt.FlushFinalBlock();
                }

                var encrypted = memoryStreamEncrypt.ToArray();

                // Return the encrypted bytes from the memory stream.
                return encrypted;
            }
        }
    }
}